package handler

import (
	"context"
	"encoding/json"
	"fmt"
	"github.com/apache/rocketmq-client-go/v2/consumer"
	"github.com/apache/rocketmq-client-go/v2/primitive"
	goredislib "github.com/go-redis/redis/v9"
	"github.com/go-redsync/redsync/v4"
	"github.com/go-redsync/redsync/v4/redis/goredis/v9"
	"github.com/golang/protobuf/ptypes/empty"
	"go.uber.org/zap"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"
	"google.golang.org/protobuf/types/known/emptypb"
	"gorm.io/gorm"
	"mxshop_srvs/inventory_srv/global"
	"mxshop_srvs/inventory_srv/model"
	"mxshop_srvs/inventory_srv/proto"
)

/*
做业务逻辑处理 基本是改动这里
*/

//var m sync.Mutex

type InventoryServer struct {
	proto.UnimplementedInventoryServer
}

// SetInv 设置库存 如果没有就新增   如果有就更新
func (*InventoryServer) SetInv(ctx context.Context, req *proto.GoodsInvInfo) (*empty.Empty, error) {
	var inv model.Inventory
	global.DB.Where(&model.Inventory{Goods: req.GoodsId}).First(&inv)
	inv.Goods = req.GoodsId
	inv.Stocks = req.Num

	global.DB.Save(&inv)
	return &emptypb.Empty{}, nil
}

func (*InventoryServer) InvDetail(ctx context.Context, req *proto.GoodsInvInfo) (*proto.GoodsInvInfo, error) {
	var inv model.Inventory
	if result := global.DB.Where(&model.Inventory{Goods: req.GoodsId}).First(&inv); result.RowsAffected == 0 {
		return nil, status.Errorf(codes.InvalidArgument, "没有库存信息")
	}
	return &proto.GoodsInvInfo{
		GoodsId: inv.Goods,
		Num:     inv.Stocks,
	}, nil
}

// Sell 扣减库存
func (*InventoryServer) Sell(ctx context.Context, req *proto.SellInfo) (*empty.Empty, error) {
	// 扣减库存, 本地事务 [1:10, 2:5, 3:20]
	// 数据库基本的一个应用场景: 数据库事务
	// 并发情况之下 可能会出现超卖
	client := goredislib.NewClient(&goredislib.Options{
		Addr: fmt.Sprintf("%s:%d", global.ServerConfig.RedisInfo.Host, global.ServerConfig.RedisInfo.Port),
	})
	pool := goredis.NewPool(client) // or, pool := redigo.NewPool(...)
	rs := redsync.New(pool)

	mutex := rs.NewMutex(fmt.Sprint("order")) // 这里以订单来进行结算
	if err := mutex.Lock(); err != nil {
		return nil, status.Errorf(codes.Internal, "[Sell] 获取 redis 分布式锁异常")
	}
	//m.Lock()
	tx := global.DB.Begin()

	sellDetail := model.StockSellDetail{
		OrderSn: req.OrderSn,
		Status:  1,
	}
	var details []model.GoodsDetail
	for _, goodInfo := range req.GoodsInfo {
		details = append(details, model.GoodsDetail{
			Goods: goodInfo.GoodsId,
			Num:   goodInfo.Num,
		})

		var inv model.Inventory
		//if result := tx.Clauses(clause.Locking{Strength: "UPDATE"}).Where(&model.Inventory{Goods: goodInfo.GoodsId}).First(&inv); result.RowsAffected == 0 {
		//	tx.Rollback() // 回滚之前的操作
		//	return nil, status.Errorf(codes.NotFound, "没有库存信息")
		//}

		//for {
		if result := global.DB.Where(&model.Inventory{Goods: goodInfo.GoodsId}).First(&inv); result.RowsAffected == 0 {
			tx.Rollback() // 回滚之前的操作
			zap.S().Debug(codes.InvalidArgument, "[Sell] 没有库存信息")
			return nil, status.Errorf(codes.InvalidArgument, "[Sell] 没有库存信息")
		}

		// 判断库存是否充足
		if inv.Stocks < goodInfo.Num {
			tx.Rollback() // 回滚之前的操作
			return nil, status.Errorf(codes.ResourceExhausted, "[Sell] 库存不足")
		}
		// 扣减 会出现数据不一致的问题 - 锁 分布式锁
		//inv.Stocks -= goodInfo.Num

		// update inventory set stocks = stocks - goodInfo.Num, version = version + 1 where goods=goods and version=version
		//if result := tx.Model(&model.Inventory{}).Select("stocks", "Version").Where("goods = ? and version = ?", goodInfo.GoodsId, inv.Version).Updates(model.Inventory{
		//	Stocks:  inv.Stocks,
		//	Version: inv.Version + 1,
		//}); result.RowsAffected == 0 {
		//	zap.S().Info("库存扣减失败")
		//} else {
		//	break
		//}
		//}

		//tx.Save(&inv)
		result := tx.Model(&model.Inventory{}).Where(&model.Inventory{Goods: inv.Goods}).Update("stocks", gorm.Expr("stocks-?", goodInfo.Num))
		if result.RowsAffected == 0 {
			tx.Rollback() // 回滚之前的操作
			return nil, status.Errorf(codes.ResourceExhausted, "[Sell] 库存更新失败")
		}
	}

	sellDetail.Detail = details
	// 写 selldetail 表
	if result := tx.Create(&sellDetail); result.RowsAffected == 0 {
		tx.Rollback()
		return nil, status.Errorf(codes.Internal, "[Sell] 保存库存扣减历史失败")
	}
	tx.Commit()
	//m.Unlock()
	if ok, err := mutex.Unlock(); !ok || err != nil {
		return nil, status.Errorf(codes.Internal, "[Sell] 释放 redis 分布式锁异常")
	}

	return &emptypb.Empty{}, nil
}

func (*InventoryServer) Reback(ctx context.Context, req *proto.SellInfo) (*empty.Empty, error) {
	// 库存归还 1. 订单超时归还 2. 订单创建失败, 归还之前扣减的库存 3. 手动归还
	tx := global.DB.Begin()
	for _, goodInfo := range req.GoodsInfo {
		var inv model.Inventory
		if result := global.DB.Where(&model.Inventory{Goods: goodInfo.GoodsId}).First(&inv); result.RowsAffected == 0 {
			tx.Rollback() // 回滚之前的操作
			return nil, status.Errorf(codes.NotFound, "没有库存信息")
		}
		// 归还库存 会出现数据不一致的问题 - 锁 分布式锁
		inv.Stocks += goodInfo.Num
		tx.Save(&inv)
	}
	tx.Commit()
	return &emptypb.Empty{}, nil
}

func AutoReback(ctx context.Context, msgs ...*primitive.MessageExt) (consumer.ConsumeResult, error) {
	// 有重复归还问题 索引应该确保幂等性 不能因为消息的重复发送导致一个订单库存归还多次
	// 为了确保幂等性 新建一张表 这张表记录 详细的订单扣减细节 已经归还细节
	type OrderInfo struct {
		OrderSn string
	}
	for i := range msgs {
		var orderInfo OrderInfo
		err := json.Unmarshal(msgs[i].Body, &orderInfo)
		if err != nil {
			zap.S().Errorf("解析json失败: %v\n", msgs[i].Body)
			return consumer.ConsumeSuccess, nil
		}

		// 去将 inv 的库存加回去 将 selldetail的status设置为2, 要在事务中进行
		tx := global.DB.Begin()
		var sellDetail model.StockSellDetail
		if result := tx.Model(&model.StockSellDetail{}).Where(&model.StockSellDetail{OrderSn: orderInfo.OrderSn, Status: 1}).First(&sellDetail); result.RowsAffected == 0 {
			return consumer.ConsumeSuccess, nil // 查询不到 此订单的商品和商品的数量
		}
		// 查询到 逐个归还库存
		for _, orderGood := range sellDetail.Detail {
			// update 语句的 update xx set stocks=stocks+2 是会锁住这行的
			if result := tx.Model(&model.Inventory{}).Where(&model.Inventory{Goods: orderGood.Goods}).Update("stocks", gorm.Expr("stocks+?", orderGood.Num)); result.RowsAffected == 0 {
				tx.Rollback()
				return consumer.ConsumeRetryLater, nil // 可能是代码问题  所以重新消费看看
			}
		}
		sellDetail.Status = 2
		if result := tx.Model(&model.StockSellDetail{}).Where(&model.StockSellDetail{OrderSn: orderInfo.OrderSn}).Update("status", 2); result.RowsAffected == 0 {
			tx.Rollback()
			return consumer.ConsumeRetryLater, nil // 可能是代码问题  所以重新消费看看
		}
		tx.Commit()
	}
	return consumer.ConsumeSuccess, nil
}

// TrySell 扣减库存
func (*InventoryServer) TrySell(ctx context.Context, req *proto.SellInfo) (*empty.Empty, error) {
	client := goredislib.NewClient(&goredislib.Options{
		Addr: fmt.Sprintf("%s:%d", global.ServerConfig.RedisInfo.Host, global.ServerConfig.RedisInfo.Port),
	})

	pool := goredis.NewPool(client) // or, pool := redigo.NewPool(...)
	rs := redsync.New(pool)

	mutex := rs.NewMutex(fmt.Sprint("order")) // 这里以订单来进行结算
	if err := mutex.Lock(); err != nil {
		return nil, status.Errorf(codes.Internal, "获取 redis 分布式锁异常")
	}
	tx := global.DB.Begin()
	for _, goodInfo := range req.GoodsInfo {
		var inv model.InventoryNew
		if result := global.DB.Where(&model.Inventory{Goods: goodInfo.GoodsId}).First(&inv); result.RowsAffected == 0 {
			tx.Rollback() // 回滚之前的操作
			return nil, status.Errorf(codes.NotFound, "没有库存信息")
		}

		if inv.Stocks < goodInfo.Num {
			tx.Rollback() // 回滚之前的操作
			return nil, status.Errorf(codes.ResourceExhausted, "库存不足")
		}
		inv.Freeze += goodInfo.Num
		tx.Save(&inv)
	}
	tx.Commit()
	if ok, err := mutex.Unlock(); !ok || err != nil {
		return nil, status.Errorf(codes.Internal, "释放 redis 分布式锁异常")
	}

	return &emptypb.Empty{}, nil
}

func (*InventoryServer) ConfirmSell(ctx context.Context, req *proto.SellInfo) (*empty.Empty, error) {
	client := goredislib.NewClient(&goredislib.Options{
		Addr: fmt.Sprintf("%s:%d", global.ServerConfig.RedisInfo.Host, global.ServerConfig.RedisInfo.Port),
	})
	pool := goredis.NewPool(client) // or, pool := redigo.NewPool(...)
	rs := redsync.New(pool)

	mutex := rs.NewMutex(fmt.Sprint("order")) // 这里以订单来进行结算
	if err := mutex.Lock(); err != nil {
		return nil, status.Errorf(codes.Internal, "获取 redis 分布式锁异常")
	}
	tx := global.DB.Begin()
	for _, goodInfo := range req.GoodsInfo {
		var inv model.InventoryNew
		if result := global.DB.Where(&model.Inventory{Goods: goodInfo.GoodsId}).First(&inv); result.RowsAffected == 0 {
			tx.Rollback() // 回滚之前的操作
			return nil, status.Errorf(codes.NotFound, "没有库存信息")
		}

		if inv.Stocks < goodInfo.Num {
			tx.Rollback() // 回滚之前的操作
			return nil, status.Errorf(codes.ResourceExhausted, "库存不足")
		}
		inv.Stocks -= goodInfo.Num
		inv.Freeze -= goodInfo.Num
		tx.Save(&inv)
	}
	tx.Commit()
	if ok, err := mutex.Unlock(); !ok || err != nil {
		return nil, status.Errorf(codes.Internal, "释放 redis 分布式锁异常")
	}

	return &emptypb.Empty{}, nil
}

func (*InventoryServer) CancelSell(ctx context.Context, req *proto.SellInfo) (*empty.Empty, error) {
	client := goredislib.NewClient(&goredislib.Options{
		Addr: fmt.Sprintf("%s:%d", global.ServerConfig.RedisInfo.Host, global.ServerConfig.RedisInfo.Port),
	})
	pool := goredis.NewPool(client) // or, pool := redigo.NewPool(...)
	rs := redsync.New(pool)

	mutex := rs.NewMutex(fmt.Sprint("order")) // 这里以订单来进行结算
	if err := mutex.Lock(); err != nil {
		return nil, status.Errorf(codes.Internal, "获取 redis 分布式锁异常")
	}
	tx := global.DB.Begin()
	for _, goodInfo := range req.GoodsInfo {
		var inv model.InventoryNew
		if result := global.DB.Where(&model.Inventory{Goods: goodInfo.GoodsId}).First(&inv); result.RowsAffected == 0 {
			tx.Rollback() // 回滚之前的操作
			return nil, status.Errorf(codes.NotFound, "没有库存信息")
		}
		if inv.Stocks < goodInfo.Num {
			tx.Rollback() // 回滚之前的操作
			return nil, status.Errorf(codes.ResourceExhausted, "库存不足")
		}
		inv.Freeze -= goodInfo.Num
		tx.Save(&inv)
	}
	tx.Commit()
	if ok, err := mutex.Unlock(); !ok || err != nil {
		return nil, status.Errorf(codes.Internal, "释放 redis 分布式锁异常")
	}

	return &emptypb.Empty{}, nil
}
